package glapp;

import java.util.ArrayList;
import org.lwjgl.opengl.GL11;

import glmodel.*;

/**
 * GLLine creates a quad-strip line from a series of points. To use:
 * 
 * <PRE>
 * draw()
 * {
 * 	GLLine L = new GLLine();
 * 	L.setWidth(20);
 * 	L.point(0, 0);
 * 	L.point(5, 5);
 * 	L.point(0, 10);
 * 	L.point(5, 15);
 * 	L.close(); // if you want a closed shape
 * 	L.draw();
 * }
 * </PRE>
 * <P>
 * NOTE: gets ugly when points are very close, or reverse direction sharply.
 * Uses my home-grown trigonometry... probably not the best way to do this, but
 * it works in most cases.
 * <P>
 * ALSO NOTE: the line will be drawn with the active texture. To draw an
 * untextured line, call activateTexture(0) or disable textures
 * (GL11.glDisable(GL11.GL_TEXTURE_2D) before calling line.draw().
 */
public class GLLine
{
	private ArrayList<GL_Vector>	points		= null;	// holds each point in line (see point())
	private float					w2			= 1f;		// half of line width
	private boolean					changed		= false;	// true if line was changed since last rebuild()
	private float					uvfactor	= 100f;	// how to tile texture on line: if uvfactor=100 texture will repeat every 100 units
	private float[][]				verts		= null;	// to hold vertex positions
	private int						vc			= 0;		// vertex counter

	public GLLine()
	{
		clear();
	}

	public GLLine(float lineWidth)
	{
		clear();
		setWidth(lineWidth);
	}

	/**
	 * reset the line
	 */
	public void clear()
	{
		points = new ArrayList<GL_Vector>();
		verts = null;
	}

	/**
	 * Add an xy point to the line.
	 */
	public void point(float x, float y)
	{
		GL_Vector p = new GL_Vector(x, y, 0);
		/*
		 * // eliminate segments shorter than 10 units // in hand drawn lines
		 * very short segments (around 1 unit long) give erratic results if
		 * (points.size() > 0) { if (
		 * GL_Vector.sub(p,(GL_Vector)points.get(points.size()-1)).length() <
		 * 10) { return; } }
		 */
		points.add(p);
		changed = true;
	}

	/**
	 * set line width
	 */
	public void setWidth(float w)
	{
		w2 = w / 2f;
		changed = true;
	}

	/**
	 * return line width
	 */
	public float getWidth()
	{
		return w2 * 2;
	}

	/**
	 * return number of points in this line
	 */
	public int getNumPoints()
	{
		return points.size();
	}

	/**
	 * return the given xy point
	 */
	public GL_Vector getPoint(int n)
	{
		return (n > 0 && n < points.size()) ? (GL_Vector) points.get(n) : null;
	}

	/**
	 * set texture tiling factor. Texture will repeat every <uvfactor> units. If
	 * a line is 50 units long, and uvfactor is 10, then the texture will repeat
	 * five times along the length of the line.
	 */
	public void setUVfactor(float tilefactor)
	{
		uvfactor = tilefactor;
		changed = true;
	}

	

	/**
	 * create the line geometry from point positions
	 */
	private void makeLine()
	{
		// need at least two points
		if (points.size() < 2)
		{
			return;
		}

		// create array to hold vertex positions and reset vert counter
		verts = new float[(points.size() * 4) - 4][3];
		vc = 0;

		GL_Vector firstP = (GL_Vector) points.get(0);
		GL_Vector lastP = (GL_Vector) points.get(points.size() - 1);

		for (int i = 0; i < points.size() - 1; i++)
		{
			GL_Vector p1 = (GL_Vector) points.get(i);
			GL_Vector p2 = (GL_Vector) points.get(i + 1);
			// default prev and next line segment directions are the same as this segment direction
			GL_Vector v1 = new GL_Vector(p2, p1);
			GL_Vector v2 = new GL_Vector(p2, p1);
			// get end directions of line segment (will change the shape of the segment so it joins with next segment)
			// special case: first segment of closed shape
			if (i == 0 && firstP.equals(lastP))
			{
				// if first point and last point are the same, first segment will join last
				v1 = new GL_Vector(lastP, (GL_Vector) points.get(points.size() - 2));
			}
			// previous line segment direction
			if (i > 0)
			{
				v1 = new GL_Vector(p1, (GL_Vector) points.get(i - 1));
			}
			// next line segment direction
			if (i < points.size() - 2)
			{
				v2 = new GL_Vector((GL_Vector) points.get(i + 2), p2);
			}
			// draw the line segment
			makeSegment(p1, p2, v1, v2);
		}

		// if first point and last point are the same, join last segment to first
		if (firstP.equals(lastP))
		{
			// move last vert to same position as first
			verts[verts.length - 1][0] = verts[0][0];
			verts[verts.length - 1][1] = verts[0][1];
			// move second to last vert to same position as second
			verts[verts.length - 2][0] = verts[1][0];
			verts[verts.length - 2][1] = verts[1][1];
		}

		changed = false;
	}

	/**
	 * Draw one segment from a line. The segment is a quad created by finding
	 * normals to the line vector. The line is drawn in the XY plane. In the
	 * diagram below, qv1 thru qv4 are the four quad vectors. N1 and N2 are
	 * normals to the line vector and Z axis. The normals are averaged with the
	 * previous and next line segments so that this segment will join gracefully
	 * with the previous and next.
	 * 
	 * <PRE>
	 *      qv4   p2    qv3
	 *        +----o----+
	 *         -N2 |  N2
	 *             |
	 *             |          line segment p1-p2
	 *             |
	 *         -N1 |  N1
	 *        +----o----+
	 *      qv1   p1    qv2
	 * </PRE>
	 * 
	 * @param p1
	 *            start point of line segment
	 * @param p2
	 *            end point of line segment
	 * @param prevV
	 *            direction of previous line segment
	 * @param postV
	 *            direction of next line segment
	 */
	private void makeSegment(GL_Vector p1, GL_Vector p2, GL_Vector prevV, GL_Vector postV)
	{
		// vectors of line segments
		GL_Vector v0 = prevV; // direction of prior line segment
		GL_Vector v1 = GL_Vector.sub(p2, p1); // direction of segment we're drawing now
		GL_Vector v2 = postV; // direction of next line segment

		// normals to form the line joins
		GL_Vector N1 = getNormal(v0, v1, w2); // average normal for previous and current segment
		GL_Vector N2 = getNormal(v1, v2, w2); // average normal for current and next segment

		// make the four quad verts for the segment
		GL_Vector qv2 = GL_Vector.add(p1, N1);
		GL_Vector qv1 = GL_Vector.add(p1, N1.reverse());
		GL_Vector qv3 = GL_Vector.add(p2, N2);
		GL_Vector qv4 = GL_Vector.add(p2, N2.reverse());

		// add the verts to array
		verts[vc][0] = qv1.x;
		verts[vc][1] = qv1.y;
		verts[vc][2] = qv1.z;
		vc++;
		verts[vc][0] = qv2.x;
		verts[vc][1] = qv2.y;
		verts[vc][2] = qv2.z;
		vc++;
		verts[vc][0] = qv3.x;
		verts[vc][1] = qv3.y;
		verts[vc][2] = qv3.z;
		vc++;
		verts[vc][0] = qv4.x;
		verts[vc][1] = qv4.y;
		verts[vc][2] = qv4.z;
		vc++;
	}

	/**
	 * Make an average normal to the two vectors with the right length to make a
	 * join for a line <width> wide.
	 * 
	 * <PRE>
	 *           + - - - - - -
	 *           |\ normal
	 *           | \
	 *           |  o-----------o line b
	 *              |
	 *              |
	 *              |
	 *              o line a
	 * </PRE>
	 * 
	 * @param a
	 * @param b
	 * @return
	 */
	private GL_Vector getNormal(GL_Vector a, GL_Vector b, float width)
	{
		// Z axis
		GL_Vector Z = new GL_Vector(0, 0, 1);
		// get normal to line a and b (normals are in the XY plane)
		GL_Vector cross1 = GL_Vector.crossProduct(a, Z);
		GL_Vector cross2 = GL_Vector.crossProduct(b, Z);
		// average the two normals
		GL_Vector crossAvg = GL_Vector.add(cross1, cross2).div(2).normalize();
		// adjust the length of the normal
		float N1 = getNormalLen(cross1, crossAvg, width);
		crossAvg.mult(N1);
		return crossAvg;
	}

	/**
	 * Return the length of the normal needed to make a join in a line of the
	 * given width.
	 * 
	 * @param v1
	 * @param v2
	 * @param width
	 * @return
	 */
	private float getNormalLen(GL_Vector v1, GL_Vector v2, float width)
	{
		float A = 180 - GL_Vector.angleXY(v1, v2);
		float normlen = width / (float) Math.cos(Math.toRadians(A));
		return normlen;
	}

	

	

	/**
	 * draw the mesh geometry created by rebuild() as a 3D shape (extrude the
	 * line shape along the Z axis).
	 */
	public void draw3D(float height)
	{
		// rebuild if points were added or width changed
		if (changed)
		{
			makeLine();
		}
		// if line is not empty, draw it
		if (verts != null && verts.length > 0)
		{
			float z = height; // how high to extrude line
			GL_Vector n; // normal of quad

			// draw the quads
			for (int i = 0; i < verts.length; i += 4)
			{
				// quad facing forward
				GL11.glNormal3f(0, 0, 1);
				GL11.glBegin(GL11.GL_QUADS);
				{
					GL11.glTexCoord2f(verts[i + 0][0] / uvfactor, verts[i + 0][1] / uvfactor);
					GL11.glVertex3f(verts[i + 0][0], verts[i + 0][1], verts[i + 0][2]);

					GL11.glTexCoord2f(verts[i + 1][0] / uvfactor, verts[i + 1][1] / uvfactor);
					GL11.glVertex3f(verts[i + 1][0], verts[i + 1][1], verts[i + 1][2]);

					GL11.glTexCoord2f(verts[i + 2][0] / uvfactor, verts[i + 2][1] / uvfactor);
					GL11.glVertex3f(verts[i + 2][0], verts[i + 2][1], verts[i + 2][2]);

					GL11.glTexCoord2f(verts[i + 3][0] / uvfactor, verts[i + 3][1] / uvfactor);
					GL11.glVertex3f(verts[i + 3][0], verts[i + 3][1], verts[i + 3][2]);
				}
				GL11.glEnd();

				// quad facing backward
				GL11.glNormal3f(0, 0, -1);
				GL11.glBegin(GL11.GL_QUADS);
				{
					GL11.glTexCoord2f(verts[i + 1][0] / uvfactor, verts[i + 1][1] / uvfactor);
					GL11.glVertex3f(verts[i + 1][0], verts[i + 1][1], verts[i + 1][2] - z);

					GL11.glTexCoord2f(verts[i + 0][0] / uvfactor, verts[i + 0][1] / uvfactor);
					GL11.glVertex3f(verts[i + 0][0], verts[i + 0][1], verts[i + 0][2] - z);

					GL11.glTexCoord2f(verts[i + 3][0] / uvfactor, verts[i + 3][1] / uvfactor);
					GL11.glVertex3f(verts[i + 3][0], verts[i + 3][1], verts[i + 3][2] - z);

					GL11.glTexCoord2f(verts[i + 2][0] / uvfactor, verts[i + 2][1] / uvfactor);
					GL11.glVertex3f(verts[i + 2][0], verts[i + 2][1], verts[i + 2][2] - z);
				}
				GL11.glEnd();

				// quad facing side 1  (calc normal from three points on side plane)
				n = GL_Vector.getNormal(new GL_Vector(verts[i + 3][0], verts[i + 3][1], verts[i + 3][2] - z), new GL_Vector(verts[i + 0]), new GL_Vector(verts[i + 3]));
				GL11.glNormal3f(n.x, n.y, n.z);
				GL11.glBegin(GL11.GL_QUADS);
				{
					GL11.glTexCoord2f(0 / uvfactor, verts[i + 0][1] / uvfactor);
					GL11.glVertex3f(verts[i + 0][0], verts[i + 0][1], verts[i + 0][2] - z);

					GL11.glTexCoord2f(z / uvfactor, verts[i + 0][1] / uvfactor);
					GL11.glVertex3f(verts[i + 0][0], verts[i + 0][1], verts[i + 0][2]);

					GL11.glTexCoord2f(z / uvfactor, verts[i + 3][1] / uvfactor);
					GL11.glVertex3f(verts[i + 3][0], verts[i + 3][1], verts[i + 3][2]);

					GL11.glTexCoord2f(0 / uvfactor, verts[i + 3][1] / uvfactor);
					GL11.glVertex3f(verts[i + 3][0], verts[i + 3][1], verts[i + 3][2] - z);
				}
				GL11.glEnd();

				// quad facing side 2 (calc normal from three points on side plane)
				n = GL_Vector.getNormal(new GL_Vector(verts[i + 1][0], verts[i + 1][1], verts[i + 1][2] - z), new GL_Vector(verts[i + 1]), new GL_Vector(verts[i + 2]));
				GL11.glNormal3f(n.x, n.y, n.z);
				GL11.glBegin(GL11.GL_QUADS);
				{
					GL11.glTexCoord2f(0 / uvfactor, verts[i + 1][1] / uvfactor);
					GL11.glVertex3f(verts[i + 1][0], verts[i + 1][1], verts[i + 1][2]);

					GL11.glTexCoord2f(z / uvfactor, verts[i + 1][1] / uvfactor);
					GL11.glVertex3f(verts[i + 1][0], verts[i + 1][1], verts[i + 1][2] - z);

					GL11.glTexCoord2f(z / uvfactor, verts[i + 2][1] / uvfactor);
					GL11.glVertex3f(verts[i + 2][0], verts[i + 2][1], verts[i + 2][2] - z);

					GL11.glTexCoord2f(0 / uvfactor, verts[i + 2][1] / uvfactor);
					GL11.glVertex3f(verts[i + 2][0], verts[i + 2][1], verts[i + 2][2]);

				}
				GL11.glEnd();
			}
		}
	}

	public void drawSector2D(GL_Vector center, int start, int end, float mult)
	{
		// normal is on Z axis
		GL11.glNormal3f(0, 0, 1);

//		int tempEnd = -1;
//		if (start > end)
//		{
//			tempEnd = end;
//			end = points.size() - 1;
//		}
		for (int i = start; i < end; i++)
		{
			GL_Vector point1 = new GL_Vector(points.get(i));
			GL_Vector point2 = new GL_Vector(points.get(i + 1));

			point1.mult(mult);
			point2.mult(mult);

			GL11.glBegin(GL11.GL_TRIANGLES);
			{
				GL11.glVertex3f(center.x, center.y, center.z);
				GL11.glVertex3f(point1.x, point1.y, point1.z);
				GL11.glVertex3f(point2.x, point2.y, point2.z);
			}
			GL11.glEnd();
		}
//		if (tempEnd != -1)
//		{
//			drawSector2D(center, 0, tempEnd, mult);
//		}
	}

	

}
